#ifndef _MATHEMATICS_ALGEBRA_EXPRESSION_FRACTION_
#define _MATHEMATICS_ALGEBRA_EXPRESSION_FRACTION_

#include "math_helper.h"
#include "expression.h"
#include "..\..\utilities\string_helper.h"
using namespace science::utilities;

#include <iostream>
#include <vector>
#include <algorithm>
#include <stdexcept>

namespace science
{
	namespace mathematics
	{
		namespace algebra
		{
			class expression_fraction
			{
			private:
				expression _numerator;
				expression _denomenator;

			public:
				expression_fraction()
				{

				}

				expression_fraction(expression numerator)
					: _numerator(numerator)
				{
					
				}

				expression_fraction(expression numerator, expression denomenator)
					: _numerator(numerator)
					, _denomenator(denomenator)
				{
				}

				expression_fraction(std::string expression_str)
				{
					unsigned int index = 0;
					*this = parse_expression_fraction(expression_str, index);
				}

				~expression_fraction() {}

			public:
				expression_fraction simplify()
				{
					return expression_fraction::simplify(this->_numerator, this->_denomenator);
				}

				expression_fraction reciprocal()
				{
					return expression_fraction(this->_denomenator, this->_numerator);
				}

				bool valid_denominator() const
				{
					return (_denomenator.get_terms_count() != 0 && _denomenator != expression::one());
				}

				double evaluate(std::map<char, double> var_values)
				{
					double result = _numerator.evaluate(var_values);
					if (valid_denominator())
					{
						double denom = _denomenator.evaluate(var_values);
						if (math_helper::double_check(denom, 0) == 0)
							throw new std::domain_error("The denominator evaluates to zero");
						result /= denom;
					}
					return result;
				}

				double evaluate(char var_name, double var_value)
				{
					double result = _numerator.evaluate(var_name, var_value);
					double denom = _denomenator.evaluate(var_name, var_value);
					if (math_helper::double_check(denom, 0) == 0)
						throw new std::domain_error("The denominator evaluates to zero");

					result /= denom;
					return result;
				}

				bool is_valid()
				{
					return _numerator.get_terms_count() != 0;
				}

			public:

				expression get_numerator() const { return _numerator; }
				expression get_denomenator() const { return _denomenator; }
				void set_numerator(expression numerator) { _numerator = numerator; *this = simplify(); }
				void set_denomenator(expression denomenator) { _denomenator = denomenator; *this = simplify(); }

				term get_max_power_term() const
				{
					term max_power = _numerator.get_max_power_term().get_variables_count() >
						_denomenator.get_max_power_term().get_variables_count() ? _numerator.get_max_power_term() :
						_denomenator.get_max_power_term();

					return max_power;
				}

			public:

				bool operator==(const expression_fraction& frc) const
				{
					return _numerator == frc.get_numerator() && _denomenator == frc.get_denomenator();
				}

				bool operator!=(const expression_fraction& frc) const
				{
					return !(this->operator==(frc));
				}

				bool operator==(const expression_fraction& frc)
				{
					return (const_cast<const expression_fraction*>(this))->operator==(frc);
				}

				bool operator!=(const expression_fraction& frc)
				{
					return (const_cast<const expression_fraction*>(this))->operator!=(frc);
				}

				expression_fraction& operator+=(const expression_fraction& rhs)
				{
					*this = this->operator+(rhs);
					return *this;
				}

				expression_fraction operator+(const expression_fraction& rhs)
				{
					return (const_cast<const expression_fraction*>(this))->operator+(rhs);
				}

				expression_fraction operator+(const expression_fraction& rhs) const
				{
					expression_fraction rsult;
					if (_denomenator == rhs._denomenator)
						rsult = expression_fraction(_numerator + rhs._numerator, _denomenator);
					else
						rsult = expression_fraction((_numerator * rhs._denomenator) + (rhs._numerator * _denomenator),
						_denomenator * rhs._denomenator);
					return rsult.simplify();
				}

				expression_fraction& operator-=(const expression_fraction& rhs)
				{
					*this = this->operator-(rhs);
					return *this;
				}

				expression_fraction operator-(const expression_fraction& rhs)
				{
					return (const_cast<const expression_fraction*>(this))->operator-(rhs);
				}

				expression_fraction operator-(const expression_fraction& rhs) const
				{
					expression_fraction rsult;
					if (_denomenator == rhs._denomenator)
						rsult = expression_fraction(_numerator - rhs._numerator, _denomenator);
					else
						rsult = expression_fraction((_numerator * rhs._denomenator) - (rhs._numerator * _denomenator),
						_denomenator * rhs._denomenator);
					return rsult.simplify();
				}

				expression_fraction& operator*=(int num)
				{
					*this = this->operator*(num);
					return *this;
				}

				expression_fraction operator*(int num)
				{
					return (const_cast<const expression_fraction*>(this))->operator*(num);
				}

				expression_fraction operator*(int num) const
				{
					expression_fraction result = expression_fraction(_numerator * num, _denomenator * num);
					return result.simplify();
				}

				expression_fraction& operator*=(const expression_fraction& rhs)
				{
					*this = this->operator*(rhs);
					return *this;
				}

				expression_fraction operator*(const expression_fraction& rhs)
				{
					return (const_cast<const expression_fraction*>(this))->operator*(rhs);
				}

				expression_fraction operator*(const expression_fraction& rhs) const
				{
					expression_fraction result = expression_fraction(_numerator * rhs._numerator, _denomenator * rhs._denomenator);
					return result.simplify();
				}

				expression_fraction& operator/=(int num)
				{
					*this = this->operator/(num);
					return *this;
				}

				expression_fraction operator/(int num)
				{
					return (const_cast<const expression_fraction*>(this))->operator/(num);
				}

				expression_fraction operator/(int num) const
				{
					if (num == 0)
						throw new std::invalid_argument("Cannot divide by zero");

					expression_fraction result = expression_fraction(_numerator / num, _denomenator / num);
					return result.simplify();
				}

				expression_fraction& operator/=(const expression_fraction& rhs)
				{
					*this = this->operator/(rhs);
					return *this;
				}

				expression_fraction operator/(const expression_fraction& rhs)
				{
					return (const_cast<const expression_fraction*>(this))->operator/(rhs);
				}

				expression_fraction operator/(const expression_fraction& rhs) const
				{
					expression_fraction result = expression_fraction(_numerator * rhs._denomenator, _denomenator * rhs._numerator);
					return result.simplify();
				}

				expression_fraction& operator+=(const term& rhs)
				{
					*this = this->operator+(rhs);
					return *this;
				}

				expression_fraction operator+(const term& rhs)
				{
					return (const_cast<const expression_fraction*>(this))->operator+(rhs);
				}

				expression_fraction operator+(const term& rhs) const
				{
					expression_fraction exp = *this;
					expression num;
					num += rhs;
					exp += expression_fraction(num);
					exp = exp.simplify();
					return exp;
				}

				expression_fraction& operator+=(const expression& rhs)
				{
					*this = this->operator+(rhs);
					return *this;
				}

				expression_fraction operator+(const expression& rhs)
				{
					return (const_cast<const expression_fraction*>(this))->operator+(rhs);
				}

				expression_fraction operator+(const expression& rhs) const
				{
					expression_fraction exp = *this;
					exp += expression_fraction(rhs);
					exp = exp.simplify();
					return exp;
				}

				expression_fraction& operator-=(const term& rhs)
				{
					*this = this->operator-(rhs);
					return *this;
				}

				expression_fraction operator-(const term& rhs)
				{
					return (const_cast<const expression_fraction*>(this))->operator-(rhs);
				}

				expression_fraction operator-(const term& rhs) const
				{
					expression_fraction exp = *this;
					expression num;
					num += rhs;
					exp -= expression_fraction(num);
					exp = exp.simplify();
					return exp;
				}

				expression_fraction& operator-=(const expression& rhs)
				{
					*this = this->operator-(rhs);
					return *this;
				}

				expression_fraction operator-(const expression& rhs)
				{
					return (const_cast<const expression_fraction*>(this))->operator-(rhs);
				}

				expression_fraction operator-(const expression& rhs) const
				{
					expression_fraction exp = *this;
					exp -= expression_fraction(rhs);
					exp = exp.simplify();
					return exp;
				}

				expression_fraction& operator*=(const term& rhs)
				{
					*this = this->operator*(rhs);
					return *this;
				}

				expression_fraction operator*(const term& rhs)
				{
					return (const_cast<const expression_fraction*>(this))->operator*(rhs);
				}

				expression_fraction operator*(const term& rhs) const
				{
					expression_fraction exp = *this;
					expression num;
					num += rhs;
					exp *= expression_fraction(num);
					exp = exp.simplify();
					return exp;
				}

				expression_fraction& operator*=(const expression& rhs)
				{
					*this = this->operator*(rhs);
					return *this;
				}

				expression_fraction operator*(const expression& rhs)
				{
					return (const_cast<const expression_fraction*>(this))->operator*(rhs);
				}

				expression_fraction operator*(const expression& rhs) const
				{
					expression_fraction exp = *this;
					exp *= expression_fraction(rhs);
					exp = exp.simplify();
					return exp;
				}

				expression_fraction& operator/=(const term& rhs)
				{
					*this = this->operator*(rhs);
					return *this;
				}

				expression_fraction operator/(const term& rhs)
				{
					return (const_cast<const expression_fraction*>(this))->operator*(rhs);
				}

				expression_fraction operator/(const term& rhs) const
				{
					expression_fraction exp = *this;
					expression num;
					num += rhs;
					exp /= expression_fraction(num);
					exp = exp.simplify();
					return exp;
				}

				expression_fraction& operator*=(double rhs)
				{
					*this = this->operator*(rhs);
					return *this;
				}

				expression_fraction operator*(double rhs)
				{
					return (const_cast<const expression_fraction*>(this))->operator*(rhs);
				}

				expression_fraction operator*(double rhs) const
				{
					expression_fraction exp = *this;
					expression num;
					num += rhs;
					exp *= expression_fraction(num);
					exp = exp.simplify();
					return exp;
				}

				expression_fraction& operator/=(double rhs)
				{
					*this = this->operator/(rhs);
					return *this;
				}

				expression_fraction operator/(double rhs)
				{
					return (const_cast<const expression_fraction*>(this))->operator/(rhs);
				}

				expression_fraction operator/(double rhs) const
				{
					if (math_helper::double_check(rhs, 0) == 0)
						throw new std::invalid_argument("Cannot divide by zero");

					expression_fraction exp = *this;
					expression num;
					num += rhs;
					exp /= expression_fraction(num);
					exp = exp.simplify();
					return exp;
				}
			public:
				static expression_fraction simplify(const expression_fraction& frc)
				{
					return expression_fraction::simplify(frc._numerator, frc._denomenator);
				}

				static expression_fraction simplify(expression numerator, expression denomenator)
				{				
					expression_fraction frc(numerator.simplify(), denomenator.simplify());
					return frc;
				}

				static expression_fraction parse_expression_fraction(std::string str, unsigned int& index)
				{
					str = string_helper::remove_white_chars(str);
					expression_fraction exp;
					if (index >= str.size())
						return exp;

					exp.set_numerator(expression::parse_expression(str, index));
					if (str[index] == '/')
					{
						index++;
						if (index >= str.size())
							throw new std::invalid_argument("Invalid expression string");
						exp.set_denomenator(expression::parse_expression(str, index));
					}

					return exp;
				}

			public:
				friend std::ostream &operator<<(std::ostream &output, const expression_fraction &frc)
				{
					if (frc.valid_denominator())
						output << "(" << frc._numerator << ")/(" << frc._denomenator << ")";
					else
						output << frc._numerator;
					return output;
				}

				friend std::istream &operator>>(std::istream  &input, expression_fraction &exp)
				{
					std::cout << "Enter numreator : " << std::endl;
					input >> exp._numerator;
					std::cout << "Enter denomenator : " << std::endl;
					input >> exp._denomenator;

					return input;
				}
			};

			static expression_fraction operator*(int num, const expression_fraction& rhs)
			{
				return rhs.operator*(num);
			}

			static expression_fraction operator/(int num, const expression_fraction& rhs)
			{
				return rhs.operator/(num);
			}

			static expression_fraction operator/(const expression& lhs, const expression& rhs)
			{
				return expression_fraction(lhs, rhs);
			}

			static expression_fraction operator+(const term& lhs, const expression_fraction& rhs)
			{
				return rhs.operator+(lhs);
			}

			static expression_fraction operator-(const term& lhs, const expression_fraction& rhs)
			{
				return rhs.operator-(lhs);
			}

			static expression_fraction operator*(const term& lhs, const expression_fraction& rhs)
			{
				return rhs.operator*(lhs);
			}

			static expression_fraction operator/(const term& lhs, const expression_fraction& rhs)
			{
				return rhs.operator/(lhs);
			}

			static expression_fraction operator*(double num, const expression_fraction& rhs)
			{
				return rhs.operator*(num);
			}

			static expression_fraction operator/(double num, const expression_fraction& rhs)
			{
				return rhs.operator/(num);
			}
		}
	}
}

#endif